#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using starLiGHT.Input;
using starLiGHT.Physic.TwoD;
using starLiGHT.Physic;
using starLiGHT.Camera.TwoD;

#endregion

namespace starLiGHT.Sandbox.PhysicEndlessScrolling
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        #region Physic Settings
        Vector2 gravity = new Vector2(0.0f, -10.0f);
        float physicIntervall = 1.0f / 60.0f;
        int velocityIterations = 8;
        int positionIterations = 3;

        #endregion

        #region Member Variables
        GraphicsDeviceManager graphics;
        InputManager inputManager;
        SpriteBatch spriteBatch;

        SpriteFont spriteFont;

        Camera2D camera;

        RigidBody2D player;
        RigidBody2D ground;
        List<Fixture> groundFixtures;
        List<RigidBody2D> droppingBodies = new List<RigidBody2D>();

        String altString;

        Random random = new Random();
        #endregion

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
            //graphics.IsFullScreen = true;
        }

        protected override void Initialize()
        {
            PhysicSystem2D.Instance.World = new World(gravity, true);

            SetupInputHandling();

            camera = new Camera2D(Vector2.Zero, new Vector2(graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight));
            camera.Zoom = 20.0f;

            PhysicSystem2D.Instance.World.SetDebugDraw(new DebugRender(GraphicsDevice,
                                                                       camera,
                                                                       AABB.Empty,
                                                                       camera.Size.X,
                                                                       camera.Size.Y,
                                                                       20.0f));

            SetupPhysicalWorld();

            base.Initialize();
        }

        void SetupInputHandling()
        {
            inputManager = new InputManager(this);
            this.Services.AddService(typeof(InputManager), inputManager);
            this.Components.Add(inputManager);

            TriggerAction ta;

            ta = inputManager.createAction("EXIT", typeof(TriggerAction)) as TriggerAction;
            ta.AddDigitalControl(inputManager.GetInputDevice(Keys.Escape, LogicalPlayerIndex.One));
            ta.AddDigitalControl(inputManager.GetInputDevice(Buttons.Back, LogicalPlayerIndex.One));

            ta = inputManager.createAction("STEER_LEFT", typeof(TriggerAction)) as TriggerAction;
            ta.AddDigitalControl(inputManager.GetInputDevice(Keys.A, LogicalPlayerIndex.One));
            ta.AddDigitalControl(inputManager.GetInputDevice(Keys.Left, LogicalPlayerIndex.One));
            ta.AddDigitalControl(inputManager.GetInputDevice(Buttons.DPadLeft, LogicalPlayerIndex.One));

            ta = inputManager.createAction("STEER_RIGHT", typeof(TriggerAction)) as TriggerAction;
            ta.AddDigitalControl(inputManager.GetInputDevice(Keys.D, LogicalPlayerIndex.One));
            ta.AddDigitalControl(inputManager.GetInputDevice(Keys.Right, LogicalPlayerIndex.One));
            ta.AddDigitalControl(inputManager.GetInputDevice(Buttons.DPadRight, LogicalPlayerIndex.One));

            ta = inputManager.createAction("STEER_UP", typeof(TriggerAction)) as TriggerAction;
            ta.AddDigitalControl(inputManager.GetInputDevice(Keys.W, LogicalPlayerIndex.One));
            ta.AddDigitalControl(inputManager.GetInputDevice(Keys.Up, LogicalPlayerIndex.One));
            ta.AddDigitalControl(inputManager.GetInputDevice(Buttons.DPadUp, LogicalPlayerIndex.One));

            ta = inputManager.createAction("STEER_DOWN", typeof(TriggerAction)) as TriggerAction;
            ta.AddDigitalControl(inputManager.GetInputDevice(Keys.S, LogicalPlayerIndex.One));
            ta.AddDigitalControl(inputManager.GetInputDevice(Keys.Down, LogicalPlayerIndex.One));
            ta.AddDigitalControl(inputManager.GetInputDevice(Buttons.DPadDown, LogicalPlayerIndex.One));

            ta = inputManager.createAction("ACTION1", typeof(TriggerAction)) as TriggerAction;
            ta.AddDigitalControl(inputManager.GetInputDevice(Keys.Space, LogicalPlayerIndex.One));
            ta.AddDigitalControl(inputManager.GetInputDevice(Buttons.A, LogicalPlayerIndex.One));
        }

        void SetupPhysicalWorld()
        {
            //
            // player character
            //

            player = PhysicSystem2D.Instance.createBox(BodyType.KINEMATIC, new Vector2(10.0f, 10.0f), new Vector2(1.0f, 0.5f));

            //
            // chain atached to player character
            //
            float y = 9.5f;
            RigidBody2D prevBody = player;
            for (Int32 i = 0; i < 15; ++i)
            {
                RigidBody2D body = PhysicSystem2D.Instance.createBox(new Vector2(10.5f + i, y),
                                                                     0.6f, 0.125f,
                                                                     0.2f, 20.0f);

                PhysicSystem2D.Instance.createRevoluteJoint(prevBody, body, new Vector2(10.0f + i, y));

                prevBody = body;
            }

            //
            // ground body
            //
            ground = PhysicSystem2D.Instance.createBody(BodyType.STATIC, Vector2.Zero);
            groundFixtures = new List<Fixture>();

            x1 = -33.0f;
            y1 = 2.0f * (float)System.Math.Cos(x1 / 10.0f * (float)System.Math.PI);
            for (int i = 0; i < 66; ++i)
            {
                groundFixtures.Add(createNextGroundFixture());
            } 
        }

        float x1;
        float y1;
        Fixture createNextGroundFixture()
        {
            Fixture ret;

            float x2 = x1 + 1.0f;
            float y2 = 2.0f * (float)System.Math.Cos(x2 / 10.0f * (float)System.Math.PI);

            ret = ground.addEdgeShape(new Vector2(x1, -15.0f + y1), new Vector2(x2, -15.0f + y2));

            x1 = x2;
            y1 = y2;

            return ret;
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            spriteFont = Content.Load<SpriteFont>(@"Arial");
        }

        protected override void Update(GameTime gameTime)
        {
            System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();

            // Allows the game to exit
            if (inputManager.IsActionPress("EXIT")) this.Exit();

            Vector2 steerVector = new Vector2(0.1f, 0.0f);
            camera.Center += steerVector;
            if (inputManager.IsActionHeld("STEER_LEFT")) steerVector.X += -0.1f;
            if (inputManager.IsActionHeld("STEER_RIGHT")) steerVector.X += 0.1f;
            if (inputManager.IsActionHeld("STEER_UP")) steerVector.Y += 0.1f;
            if (inputManager.IsActionHeld("STEER_DOWN")) steerVector.Y += -0.1f;

            //
            // drop stuff
            //
            if (inputManager.IsActionPress("ACTION1"))
            {
                float x = random.NextFloat(player.Position.X - 5.0f, player.Position.X + 5.0f);
                RigidBody2D body = PhysicSystem2D.Instance.createCircle(new Vector2(x, 19.0f), 0.25f);
                droppingBodies.Add(body);
                if (droppingBodies.Count > 30)
                {
                    RigidBody2D bodyToRemove = droppingBodies[0];
                    droppingBodies.RemoveAt(0);
                    PhysicSystem2D.Instance.destroyBody(bodyToRemove);
                }
            }

            //
            // remove ground elements at the left and add a new one at the right
            //
            if (camera.ViewportPosition.X > groundFixtures[0].GetAABB(0).lowerBound.X + 33.0f)
            {
                Fixture fixtureToRemove = groundFixtures[0];
                groundFixtures.RemoveAt(0);
                ground.Body.DestroyFixture(fixtureToRemove);
                groundFixtures.Add(createNextGroundFixture());
            }

            if (steerVector != Vector2.Zero)
                player.Body.SetTransform(player.Body.Position + steerVector, 0.0f);

            PhysicSystem2D.Instance.World.Step(physicIntervall, velocityIterations, positionIterations);

            //
            // do a ray cast towards the ground
            //
            bool m_hit = false;
            Vector2 point1 = player.Body.Position;
            Vector2 point2 = point1 + new Vector2(0.0f, -50.0f);

            Vector2 m_point = Vector2.Zero;
            Vector2 m_normal = Vector2.Zero;

            PhysicSystem2D.Instance.World.RayCast((fixture, p, n, fr) =>
                {
                    Body body = fixture.GetBody();
                    m_hit = true;
                    m_point = p;
                    m_normal = n;
                    return fr;
                }
            , point1, point2);

            if (m_hit)
            {
                altString = String.Format("Altitude: {0}", ((m_point - point1).Length() * camera.Zoom));
            }
            else
            {
                altString = "Altitude: ray didn't hit ground";
            }

            sw.Stop();

            updateTime = sw.ElapsedMilliseconds.ToString();

            base.Update(gameTime);
        }

        String updateTime;

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            PhysicSystem2D.Instance.World.DebugDraw.updateCamera(camera, true);
            PhysicSystem2D.Instance.World.DrawDebugData();

            spriteBatch.Begin();
            spriteBatch.DrawString(spriteFont,
                                   altString + "\nUpdate: " + updateTime + "ms",
                                   new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X, GraphicsDevice.Viewport.TitleSafeArea.Y), //TODO: THIS IS A HUGE PERFORMANCE PROBLEM ON XBOX!!! 
                                   Color.White);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
